//
//  SDCache.m
//  SDCache
//
//  Created by yoxnet on 15/11/23.
//  Copyright © 2015年 yoxnet. All rights reserved.
//

#import "SDCache.h"
#import <CommonCrypto/CommonDigest.h>
#import <objc/runtime.h>
#import "Model.h"

#define mDefaultCachePath @"com.972809971.SDCache"//默认的缓存路径，在Library/Caches文件夹下

static const NSUInteger mDefaultCacheMaxAge = 60*60*24*7;//缓存最长时间为一周

@interface SDCache()

@property(nonatomic,strong) dispatch_queue_t sdQueue;

@property(nonatomic,strong)NSCache * memoryCache;

@property(nonatomic,strong) NSString * diskCachePath;


@end


@implementation SDCache
{
    NSFileManager * _fileManager;
}

static dispatch_once_t once;
static SDCache * cache;

+(instancetype)shareSDCacheManager;
{
    
    
    dispatch_once(&once, ^{
        
        cache = [[SDCache alloc]init];
        
        NSLog(@"sd path %@",cache.diskCachePath);
        
    });
    return cache;
}

//
+(void)releaseShareSDCacheManager;
{
    if (cache) {
        cache = nil;
    }
}

-(void)dealloc
{
    _sdQueue = nil;
    _memoryCache = nil;
    
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
}
-(instancetype)init
{
    if (self=[super init]) {
        
        _sdQueue = dispatch_queue_create("com.972809971.SDCache", DISPATCH_QUEUE_SERIAL);
        _memoryCache = [[NSCache alloc]init];
        _cacheMaxAge = mDefaultCacheMaxAge;
        _fileManager = [NSFileManager defaultManager];
        _diskCachePath = [self makeDiskCachePath];
        
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];
        
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(cleanDisk)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];
        
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundCleanDisk)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
        
    }
    return self;
}

#pragma mark------private method

//缓存在磁盘的路径
-(NSString *)makeDiskCachePath
{
    NSArray * userPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    
    NSString * fatherCachePath = userPath.firstObject;
    
    BOOL isDir,existFile;
    NSString * cachePath = [fatherCachePath stringByAppendingPathComponent:mDefaultCachePath];
    existFile =[_fileManager fileExistsAtPath:cachePath isDirectory:&isDir];

    if (!(existFile&&isDir)) {
        
        [_fileManager createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:nil];
    }
    return cachePath;
}

//由key转化为file名
-(NSString *)fileNameFromKey:(NSString *)key
{
    if(key == nil || [key length] == 0)
        return nil;
    
    const char *value = [key UTF8String];
    unsigned char outputBuffer[CC_MD5_DIGEST_LENGTH];
    CC_MD5(value,(CC_LONG)strlen(value), outputBuffer);
    NSMutableString *outputString = [[NSMutableString alloc] initWithCapacity:CC_MD5_DIGEST_LENGTH];
    
    for(NSInteger count = 0; count < CC_MD5_DIGEST_LENGTH; count++){
        
        [outputString appendFormat:@"%02x",outputBuffer[count]];
        
    }
    return outputString;
}


-(NSString *)defaultFileDiskNameForKey:(NSString *)key
{
    
    return [self.diskCachePath stringByAppendingPathComponent:[self fileNameFromKey:key]];
    
}


#pragma mark------public method

-(void)clearMemory
{
    [self.memoryCache removeAllObjects];
}
-(void)cleanDisk
{
    [self cleanDiskWithCompletionBlock:nil];
}

- (void)clearSDCacheOnCompletion:(void(^)(void))completion  options:(SDCacheType)type;
{
    if (type&SDCacheTypeMemory) {
        [self clearMemory];
    }
    
    if (type&SDCacheTypeDisk) {
        
        [self clearSDCacheDiskOnCompletion:completion];
    }
}


- (void)clearSDCacheDiskOnCompletion:(void(^)(void))completion
{
    dispatch_async(self.sdQueue, ^{
        [_fileManager removeItemAtPath:self.diskCachePath error:nil];
        [_fileManager createDirectoryAtPath:self.diskCachePath
                withIntermediateDirectories:YES
                                 attributes:nil
                                      error:NULL];
        
        if (completion) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion();
            });
        }
    });
}
//程序在进入后台或者终止时，删除过期文件
- (void)cleanDiskWithCompletionBlock:(void(^)(void))completionBlock {
    
    dispatch_async(self.sdQueue, ^{
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
        NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
        
        // This enumerator prefetches useful properties for our cache files.
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                    options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];
        
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.cacheMaxAge];
        NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
        NSUInteger currentCacheSize = 0;
        
        // Enumerate all of the files in the cache directory.  This loop has two purposes:
        //
        //  1. Removing files that are older than the expiration date.
        //  2. Storing file attributes for the size-based cleanup pass.
        
        NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
        for (NSURL *fileURL in fileEnumerator) {
            
            NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];
            
            // Skip directories.
            if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
                continue;
            }
            
            // Remove files that are older than the expiration date;
            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
            
            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                [urlsToDelete addObject:fileURL];
                continue;
            }
            
            // Store a reference to this file and account for its total size.
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
            [cacheFiles setObject:resourceValues forKey:fileURL];
        }
        
        for (NSURL *fileURL in urlsToDelete) {
            [_fileManager removeItemAtURL:fileURL error:nil];
        }
        
 
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
}


-(void)backgroundCleanDisk
{
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
    __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    
    // Start the long-running task and return immediately.
    [self cleanDiskWithCompletionBlock:^{
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
}
-(void)storeCustomObjectSDCache:(id)obj forkey:(NSString *)key;
{
    [self storeCustomObjectSDCache:obj forkey:key options:SDCacheTypeMemoryAndDisk];
}

-(void)storeCustomObjectSDCache:(id)obj forkey:(NSString *)key options:(SDCacheType)type;
{
    if(type&SDCacheTypeMemory)
    {
        [self.memoryCache setObject:obj forKey:key];
    }
    
   
    if(type&SDCacheTypeDisk)
    {
        
        NSMutableDictionary * mutalbDict = [[NSMutableDictionary alloc]init];
        
        unsigned int count;
        objc_property_t * propertyList = class_copyPropertyList([obj class], &count);
        
        for (int i = 0; i<count; i++) {
            
            NSString * propertyName = [NSString stringWithUTF8String:property_getName(propertyList[i])];
            [mutalbDict setValue:[obj valueForKey:propertyName] forKey:propertyName];
            
        }
        
        
         NSDictionary * dict = @{@"className":NSStringFromClass([obj class]),@"content":mutalbDict};
        [self storeObjectSDCache:dict forKey:key options:SDCacheTypeDisk];
    }
}

-(void)storeObjectSDCache:(id)obj forkey:(NSString *)key;
{
    [self storeObjectSDCache:obj forKey:key options:SDCacheTypeMemoryAndDisk];
    
}

-(void)storeObjectSDCache:(id)obj forKey:(NSString *)key options:(SDCacheType)type;
{
    if ( !obj || !key) {
        
        return;
    }
    
    if (type&SDCacheTypeMemory) {
        
        [self.memoryCache setObject:obj forKey:key];
        
    }
    
    if (type&SDCacheTypeDisk) {
        
        dispatch_async(self.sdQueue, ^{
            
            NSError * error; NSData * data;
            
            if ([obj isKindOfClass:[NSString class]]) {
                
                NSString * string = (NSString *)obj;
                data = [string dataUsingEncoding:NSUTF8StringEncoding];
                
            }else if ([obj isKindOfClass:[NSDictionary class]]||[obj isKindOfClass:[NSArray class]]){
                
                data = [NSJSONSerialization dataWithJSONObject:obj options:NSJSONWritingPrettyPrinted error:&error];
                NSLog(@"json to data %@  json %@",error,[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
    
            }else{
                data=(NSData *)obj;
            }
            
            if (data) {
                if (![_fileManager fileExistsAtPath:_diskCachePath]) {
                    [_fileManager createDirectoryAtPath:_diskCachePath
                            withIntermediateDirectories:YES
                                             attributes:nil
                                                  error:NULL];
                }
                NSString * filePath = [self defaultFileDiskNameForKey:key];
                
                //先删除，再存储
                if ([_fileManager fileExistsAtPath:filePath]) {
                    
                    [_fileManager removeItemAtPath:filePath error:nil];
                    
                }
                [_fileManager createFileAtPath:[self defaultFileDiskNameForKey:key] contents:data attributes:nil];
            }
        });
        
    }
    
    
}

#pragma mark -----search
-(nullable id)objectInCacheForKey:(NSString *)key;
{
    return [self objectInCacheForKey:key options:SDCacheTypeMemoryAndDisk isData:NO];
}

-(id)objectInCacheForKey:(NSString *)key isData:(BOOL)isData;
{
    return [self objectInCacheForKey:key options:SDCacheTypeMemoryAndDisk isData:isData];
}

-(nullable id)objectInCacheForKey:(NSString *)key options:(SDCacheType)type isData:(BOOL)isData;
{
    if(nil==key)
        return nil;
    
    if (type&SDCacheTypeMemory) {

        if ([self.memoryCache objectForKey:key])
            return [self.memoryCache objectForKey:key];
        
    }
    
    if (type&SDCacheTypeDisk) {
    
        NSObject * obj = [self objectInDiskForKey:key isData:isData];
        if (obj) {
            [self.memoryCache setObject:obj forKey:key];
            return obj;
        }
    }
    return nil;
}



/**查找自定义对象*/
-(id)customObjectInCacheForKey:(NSString *)key;
{
    return [self customObjectInCacheForKey:key options:SDCacheTypeMemoryAndDisk];
}

-(id)customObjectInCacheForKey:(NSString *)key options:(SDCacheType)type;
{
    NSObject * customObject ;
    if (type&SDCacheTypeMemory) {
        customObject=[self.memoryCache objectForKey:key];
        if(customObject)
        return customObject;
    }
    if (type&SDCacheTypeDisk) {
        
        NSDictionary * dict = [self objectInDiskForKey:key isData:NO];
        
        if (dict&&[dict isKindOfClass:[NSDictionary class]]) {

            Class cls = NSClassFromString(dict[@"className"]);
            customObject = [[cls alloc]init];
            [customObject setValuesForKeysWithDictionary:dict[@"content"]];
            
            [self.memoryCache setObject:customObject forKey:key];
            return customObject;
        }
        
    }
    return nil;
}

//从磁盘文件查找key返回的对象
-(nullable id)objectInDiskForKey:(NSString *)key isData:(BOOL)isData;
{
    NSData * data = [self searchDataFromDiskKey:key];
    if(isData)
        return data;
    
    if (data) {
        
        NSString * content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        if (content) {
            
            NSError * error;
            NSObject * obj = [NSJSONSerialization JSONObjectWithData:data
                                                             options:NSJSONReadingMutableContainers
                                                               error:&error];
            
            if (error) {
//                NSLog(@"SDCache %@",error);
                return content;
            }else{
                return obj;
            }
            
        }else{
            return data;
        }
        
    }else{
        
        return nil;
    }

    return nil;
}

-(NSData *)searchDataFromDiskKey:(NSString *)key
{
    NSString * keyPath = [self defaultFileDiskNameForKey:key];
    
    NSData * data = [NSData dataWithContentsOfFile:keyPath];
    
    return data;
    
}

#pragma mark----delete

-(void) removeItemInCacheForKey:(NSString *)key;
{
    [self removeItemInCacheForKey:key options:SDCacheTypeMemoryAndDisk onCompletion:nil];
}

-(void) removeItemInCacheForKey:(NSString *)key onCompletion:(void(^)(NSError * error))completion;
{
    [self removeItemInCacheForKey:key options:SDCacheTypeMemoryAndDisk onCompletion:completion];
}

-(void) removeItemInCacheForKey:(NSString *)key
                        options:(SDCacheType)type
                   onCompletion:(void(^)(NSError * error))completion;
{
    if (key ==nil) {
        return;
    }
    
    if (type&SDCacheTypeMemory) {
        
        [self.memoryCache removeObjectForKey:key];
    }
    
    if (type&SDCacheTypeDisk) {
        dispatch_async(self.sdQueue, ^{
            
            NSError * error;
            [_fileManager removeItemAtPath:[self defaultFileDiskNameForKey:key] error:&error];
            
            
            if (completion) {
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion(error);
                });
                
            }
        });
        
    }
    
}

#pragma mark ----file size
- (NSUInteger)getSDCacheSize
{
    __block NSUInteger size = 0;
    dispatch_sync(self.sdQueue, ^{
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
        for (NSString *fileName in fileEnumerator) {
            NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
            NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
            size += [attrs fileSize];
        }
    });
    return size;
}


- (NSUInteger)getSDCacheDiskCount {
    __block NSUInteger count = 0;
    dispatch_sync(self.sdQueue, ^{
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
        count = [[fileEnumerator allObjects] count];
    });
    return count;
}


- (void)calculateSDCacheSizeWithCompletionBlock:(void(^)(NSUInteger,NSUInteger))completionBlock {

    NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
    
    dispatch_async(self.sdQueue, ^{
        NSUInteger fileCount = 0;
        NSUInteger totalSize = 0;
        
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:@[NSFileSize]
                                                options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];
        
        for (NSURL *fileURL in fileEnumerator) {
            NSNumber *fileSize;
            [fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
            totalSize += [fileSize unsignedIntegerValue];
            fileCount += 1;
        }
        
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock(fileCount, totalSize);
            });
        }
    });
}

/**归档对象到磁盘*/
-(void)archiveObjectSDCache:(id)obj forKey:(NSString *)key;
{
    dispatch_async(self.sdQueue, ^{
        
        NSString * path =[self defaultFileDiskNameForKey:key];
        
        [NSKeyedArchiver archiveRootObject:obj toFile:path];
        
    });
}
/**解归档对象*/

-(id)unarchiveObjectFromCacheForKey:(NSString *)key;
{
    NSString * path = [self defaultFileDiskNameForKey:key];
    
    return [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    
}























@end
